﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Office.Core;
using System.Reflection;
using OutlookTags.Native;
using System.Runtime.InteropServices;

namespace OutlookTags
{
    public class CommandBarEdit : NativeWindow
    {
        const string OutlookWindowClass = "rctrl_renwnd32";
        const string EditWindowClass = "RichEdit20W";
        const string DefaultText = "Type here to Tag";
        protected CommandBarControl Control { get; set; }
        Timer timer;
        string _text = String.Empty;

        #region Events
        public event EventHandler Enter;
        public event EventHandler Leave;
        public event EventHandler Char;
        public event KeyEventHandler KeyDown;
        public event KeyEventHandler KeyUp;
        public event KeyEventHandler AfterKeyDown;
        public event KeyEventHandler AfterKeyUp;
        #endregion

        public CommandBarEdit(CommandBarControl control)
        {
            Control = control;


            Text = DefaultText;

            // this timer is
            timer = new Timer();
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = 10;
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            timer.Stop();

            foreach (var hOutlook in new WindowClassEnumerator(IntPtr.Zero, OutlookWindowClass, null))
            {
                foreach (var hCommandBarDock in new WindowClassEnumerator(hOutlook, "MsoCommandBarDock", null))
                {
                    foreach (var hCommandBar in new WindowClassEnumerator(hCommandBarDock, "MsoCommandBar", null))
                    {
                        string commandBarName = SafeNativeMethods.GetWindowText(hCommandBar);
                        if (commandBarName == Control.Parent.Name)
                        {
                            foreach (var hEdit in new WindowClassEnumerator(hCommandBar, EditWindowClass, null))
                            {
                                Initialize(hEdit);
                                return;
                            }
                        }
                    }
                }
            }
        }

        private void Initialize(IntPtr hEdit)
        {
            this.AssignHandle(hEdit);
            SetTextMode(TextMode.TM_MULTICODEPAGE | TextMode.TM_MULTILEVELUNDO | TextMode.TM_RICHTEXT);
        }

        public string Text
        {
            get
            {                
                return Control.GetType().InvokeMember("Text", BindingFlags.GetProperty, null, Control, null).ToString();
            }
            set
            {                
                Control.GetType().InvokeMember("Text", BindingFlags.SetProperty, null, Control, new object[] { value });
                if (this.Handle != IntPtr.Zero)
                {
                    SafeNativeMethods.SendMessage(this.Handle, (int)SafeNativeMethods.WindowsMessages.WM_SETTEXT, IntPtr.Zero, value);
                }
            }
        }

        public void SetSelectRange(CHARRANGE range)
        {
            SafeNativeMethods.SendMessage(this.Handle, (int)RichMessages.EM_EXSETSEL, IntPtr.Zero, ref range);
        }

        public CHARRANGE GetSelectRange()
        {
            CHARRANGE range = new CHARRANGE();
            SafeNativeMethods.SendMessage(this.Handle, (int)RichMessages.EM_EXGETSEL, IntPtr.Zero, ref range);
            return range;
        }

        public bool Focused
        {
            get { return SafeNativeMethods.GetFocus() == this.Handle; }
        }

        public void Focus()
        {
            SafeNativeMethods.SetFocus(this.Handle);
        }

        private TextMode GetTextMode()
        {
            return (TextMode)SafeNativeMethods.SendMessage(this.Handle, (int)RichMessages.EM_GETTEXTMODE, IntPtr.Zero, IntPtr.Zero).ToInt32();
        }

        private void SetTextMode(TextMode textMode)
        {
            var tmpText = Text;
            Text = "";
            SafeNativeMethods.SendMessage(this.Handle, (int)SafeNativeMethods.WindowsMessages.WM_SETTEXT, IntPtr.Zero, "");
            var res = SafeNativeMethods.SendMessage(this.Handle, (int)RichMessages.EM_SETTEXTMODE, textMode, IntPtr.Zero);

            if (Marshal.GetLastWin32Error() != 0)
            {
                throw new Exception();//TODO: throw correct exception
            }
            Text = tmpText;
        }

        protected override void WndProc(ref Message m)
        {            
            switch (m.Msg)
            {
                case (int)SafeNativeMethods.WindowsMessages.WM_SETFOCUS:
                    OnEnter();
                    break;
                case (int)SafeNativeMethods.WindowsMessages.WM_KILLFOCUS:
                    OnLeave();
                    break;
                              
                case (int)SafeNativeMethods.WindowsMessages.WM_KEYDOWN:
                    if (!OnKeyDown((Keys)m.WParam.ToInt32()))
                        return;
                    break;                
                case (int)SafeNativeMethods.WindowsMessages.WM_KEYUP:
                    if (!OnKeyUp((Keys)m.WParam.ToInt32()))
                        return;
                    break;
                case (int)SafeNativeMethods.WindowsMessages.WM_SYSKEYDOWN:
                    if (!OnSysKeyDown((Keys)m.WParam.ToInt32())) 
                        return;
                    break;
                case (int)SafeNativeMethods.WindowsMessages.WM_SYSKEYUP:
                    if (!OnSysKeyUp((Keys)m.WParam.ToInt32()))
                        return;
                    break;
            }
            base.WndProc(ref m);

            switch (m.Msg)
            {
                case (int)SafeNativeMethods.WindowsMessages.WM_KEYDOWN:
                    OnAfterKeyDown((Keys)m.WParam.ToInt32());
                    break;
                case (int)SafeNativeMethods.WindowsMessages.WM_KEYUP:
                    OnAfterKeyUp((Keys)m.WParam.ToInt32());
                    break;
                case (int)SafeNativeMethods.WindowsMessages.WM_CHAR:
                case (int)SafeNativeMethods.WindowsMessages.WM_UNICHAR:
                    OnChar();
                    break; 

            }
            
        }

        protected virtual bool OnSysKeyUp(Keys key)
        {
            return true;
        }

        protected virtual bool OnSysKeyDown(Keys key)
        {
            return true;
        }

        protected virtual void OnEnter()
        {
            EventHandler tmp = Enter;
            if (tmp != null)
            {
                tmp(this, EventArgs.Empty);
            }
        }

        protected virtual void OnLeave()
        {
            EventHandler tmp = Leave;
            if (tmp != null)
            {
                tmp(this, EventArgs.Empty);
            }
        }

        protected virtual void OnChar()
        {
            EventHandler tmp = Char;
            if (tmp != null)
            {
                tmp(this, EventArgs.Empty);
            }
        }

        protected virtual bool OnKeyDown(Keys key)
        {
            KeyEventHandler tmp = KeyDown;
            if (tmp != null)
            {                
                tmp(this, new KeyEventArgs(key));
            }

            return true;
        }

        protected virtual bool OnKeyUp(Keys key)
        {
            KeyEventHandler tmp = KeyUp;
            if (tmp != null)
            {                
                tmp(this, new KeyEventArgs(key));
            }

            return true;
        }


        protected virtual bool OnAfterKeyDown(Keys key)
        {
            KeyEventHandler tmp = AfterKeyDown;
            if (tmp != null)
            {
                tmp(this, new KeyEventArgs(key));
            }

            return true;
        }

        protected virtual bool OnAfterKeyUp(Keys key)
        {
            KeyEventHandler tmp = AfterKeyUp;
            if (tmp != null)
            {
                tmp(this, new KeyEventArgs(key));
            }

            return true;
        }
        class WriteRtfData
        {
            public byte[] Buffer;
            public int Position;
        }

        public void WriteRtf(string text)
        {
            EDITSTREAM stream = new EDITSTREAM();
            stream.pfnCallback = new EditStreamCallback(StreamInCallback);
            WriteRtfData data = new WriteRtfData();
            data.Buffer = ASCIIEncoding.ASCII.GetBytes(text);
            GCHandle handle = GCHandle.Alloc(data);

            try
            {
                stream.dwCookie = GCHandle.ToIntPtr(handle);

                SafeNativeMethods.SendMessage(this.Handle, (int)RichMessages.EM_STREAMIN, StreamFormats.SF_RTF, ref stream);
            }
            finally
            {
                handle.Free();
            }
        }

        int StreamInCallback(IntPtr dwCookie, IntPtr pbBuff, int cb, IntPtr pcb)
        {
            WriteRtfData data = (WriteRtfData)(GCHandle.FromIntPtr(dwCookie)).Target;

            int length = data.Buffer.Length;
            int min = Math.Min(cb, length - data.Position);

            Marshal.Copy(data.Buffer, data.Position, pbBuff, min);
            Marshal.WriteInt32(pcb, min);
            data.Position += min;
            return 0;
        }


        public string GetText()
        {
            return SafeNativeMethods.GetWindowText(this.Handle);
        }
    }
}
